﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Data;
using ListItemEditor.UI;
using System.ComponentModel;

namespace ListItemEditor
{
    /// <summary>
    /// Portions of this code were taken from here - http://www.codeproject.com/KB/office/DataGridViewCopyPaste.aspx, many thanks to the author
    /// </summary>
    public class ClipboardHelper
    {
        private static object GetFormattedValue(object value, int rowIndex, ref DataGridViewCellStyle cellStyle, TypeConverter valueTypeConverter, TypeConverter formattedValueTypeConverter, DataGridViewDataErrorContexts context)
        {
            return value == null ? string.Empty : value.ToString();
        }

        public static void CopyClipboard(DataGridView grid)
        {
            DataGridViewComboBoxCellEx.GlobalGetFormattedValue = new DataGridViewComboBoxCellEx.GetFormattedValueImpl(GetFormattedValue);
            DataGridViewMultiSelectCell.GlobalGetFormattedValue = new DataGridViewMultiSelectCell.GetFormattedValueImpl(GetFormattedValue);

            try
            {
                // TODO - another problem here - if the cell contains \r\n or \t - the clipboard string generated by grid.GetClipboardContent() is not correct - custom solution is needed
                DataObject d = grid.GetClipboardContent();

                // fix the copy data - when you copy a whole line, there is an extra tab in front of the data
                if (d.ContainsText())
                {
                    string contents = d.GetText();
                    List<List<string>> lines = ParseCSV(contents, '\t', true);
                    if (lines.Count > 0 && grid.ColumnCount > 0 && lines[0].Count > grid.ColumnCount)
                    {
                        List<List<string>> newLines = lines.Select(l => l.GetRange(1, l.Count - 1)).ToList();
                        contents = FormatCSV(newLines, '\t');
                        d.SetText(contents);
                    }
                }

                Clipboard.SetDataObject(d);
            }
            catch (Exception ex)
            {
                MessageBox.Show(grid.Parent, ex.ToString());
            }
            finally
            {
                DataGridViewComboBoxCellEx.GlobalGetFormattedValue = null;
                DataGridViewMultiSelectCell.GlobalGetFormattedValue = null;
            }
        }

        public static void PasteClipboard(DataGridView grid)
        {
            try
            {
                // TODO - if the columns' indices and display indices differ the paste functionality will break - the columns should be iterated by display index
                if (grid.DataSource == null || grid.CurrentCell == null) return;

                string contents = Clipboard.GetText();
                if (string.IsNullOrEmpty(contents)) return;

                List<List<string>> csvData = ParseCSV(contents, '\t', true);

                int iRow = grid.CurrentCell.RowIndex;
                int iCol = grid.CurrentCell.ColumnIndex;

                CurrencyManager cm = (CurrencyManager)grid.BindingContext[grid.DataSource, grid.DataMember];
                DataView dataView = cm.List as DataView;
                // actually since the reordeing happens immediately after the sorted column is changed all the columns after the sorted column may be updated with the values of another line
                // so rows may end up with cell values from two different rows
                // i can only imagine what will happen with the client ordering is on more than one column
                if (dataView != null && !string.IsNullOrEmpty(dataView.Sort))
                {
                    if (MessageBox.Show(grid.Parent, "You have client sorting in the grid. When rows are changed they will be automatically reordered and the paste operation can overwrite random rows leading to unpredictable results. Do you want to continue?", "Waring", MessageBoxButtons.YesNo)
                        == DialogResult.No) return;
                }

                foreach (List<string> line in csvData)
                {
                    if (iRow >= grid.RowCount) break;
                    bool isNewRow = grid.Rows[iRow].IsNewRow;
                    if (isNewRow) 
                    {
                        // for new rows after the 1st new row add row in the CurrencyManager
                        if (iRow != grid.CurrentCell.RowIndex) cm.AddNew(); 
                    }

                    for (int i = 0; i < line.Count; ++i)
                    {
                        if (iCol + i >= grid.ColumnCount) break;
                        DataGridViewCell oCell = grid[iCol + i, iRow];

                        // skip readonly cells
                        if (oCell.ReadOnly) continue;

                        string sCell = line[i];
                        if (!sCell.Equals(oCell.Value))
                        {
                            if (string.IsNullOrEmpty(sCell) && oCell.ValueType != typeof(string)) oCell.Value = DBNull.Value;
                            else oCell.Value = Convert.ChangeType(sCell, oCell.ValueType);
                            
                        }
                    }
                    if (isNewRow) 
                    {
                        // for the first new row force the passing of the row to the CurrencyManager only after the cells' values are set
                        // this solves an issue when you paste new rows with client sorting in the grid
                        if (iRow == grid.CurrentCell.RowIndex) cm.EndCurrentEdit();
                    }

                    //TODO? - this can as well be called after each row is updated, then the check whether we have new line will be unnecessary
                    //cm.EndCurrentEdit();

                    iRow++;
                }
                grid.EndEdit();
                cm.EndCurrentEdit();
                grid.Invalidate();
            }
            catch (FormatException)
            {
                MessageBox.Show(grid.Parent, "The data you pasted is in the wrong format for the cell");
            }
            catch (Exception ex)
            {
                MessageBox.Show(grid.Parent, ex.ToString());
            }
        }

        private static string FormatCSV(List<List<string>> lines, char separator)
        {
            string sep = separator.ToString();
            string contents = 
                string.Join ("\r\n",
                lines.Select(
                l =>
                    string.Join (sep,
                        l.Select(s => s.Contains('\r') || s.Contains('\n') || s.Contains(separator) ? "\"" + s.Replace("\"", "\"\"") + "\"" : s).ToArray())
                    ).ToArray()
                );
            return contents;
        }

        private static List<List<string>> ParseCSV(string contents, char separator, bool ignoreQuotes)
        {
            List<List<string>> lines = new List<List<string>>();
            if (!contents.EndsWith("\r\n")) contents += "\r\n";
            int pos = 0;
            for (; ; )
            {
                List<string> values = GetLineValues(contents, separator, ref pos, ignoreQuotes);
                if (values == null) break;
                lines.Add(values);
            }

            return lines;
        }

        private static List<string> GetLineValues(string contents, char separator, ref int pos, bool ignoreQuotes)
        {
            int len = contents.Length;
            if (pos >= len) return null;

            List<string> values = new List<string>();
            bool inQuotes = false;
            StringBuilder currentToken = new StringBuilder();
            while (pos < len)
            {
                char c = contents[pos];

                if (inQuotes)
                {
                    if (c == '"')
                    {
                        // double quotes
                        if (pos + 1 < len && contents[pos + 1] == '"')
                        {
                            currentToken.Append(c);
                            pos++;
                        }
                        else inQuotes = false;
                    }
                    else currentToken.Append(c);
                }
                else
                {
                    if (c == '"' && !ignoreQuotes) inQuotes = true;
                    else if (c == separator || c == '\n' || c == '\r' || pos + 1 >= len)
                    {
                        values.Add(currentToken.ToString());
                        if (c == separator) currentToken = new StringBuilder();
                        else
                        {
                            if (c == '\r' && (pos + 1 < len && contents[pos + 1] == '\n')) pos++;
                            pos++; break;
                        }
                    }
                    else currentToken.Append(c);
                }

                pos++;
            }

            return values;
        }

    }
}
